/**
 * Copyright 2012 Alfredo "Rainbowbreeze" Morresi
 * 
 * This file is part of Eureka! project.
 * 
 * Eureka! is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * Eureka! is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with Eureka!. If not, see http://www.gnu.org/licenses/.
 */
package it.rainbowbreeze.eureka.ui.view;

import it.rainbowbreeze.eureka.common.AppEnv;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;

/**
 * Useful resources
 * http://pa.rezendi.com/2010/03/responding-to-zooms-and-pans-in.html
 * http://bricolsoftconsulting.com/2011/10/31/extending-mapview-to-add-a-change-event/
 * http://stackoverflow.com/questions/1773717/android-maps-how-to-determine-map-center-after-a-drag-has-been-completed
 * http://stackoverflow.com/questions/2328650/how-can-i-detect-if-an-android-mapview-has-been-panned-or-zoomed
 * 
 * To manage overlay items
 * http://code.google.com/p/mapview-overlay-manager
 * 
 * @author Alfredo "Rainbowbreeze" Morresi
 *
 */
public class RainbowMapView extends MapView {
    // ------------------------------------------ Private Fields
    private static final String LOG_HASH = RainbowMapView.class.getSimpleName();

    private OnChangeListener mChangeListener;
    private GeoPoint mLastMapCenter;
    private int mLastZoomLevel;

    /** Pan / zoom of the map in progress */
    private boolean mIsTouched;
    /** Manual pan / zoom is ended or not*/
    private boolean mIsTouchEnded;
    
    /** microdegrees tolerance when detection a span action */
    private boolean mEnableSpanTolerance;

    // -------------------------------------------- Constructors
    public RainbowMapView(Context context, String apiKey) {
        super(context, apiKey);
    }

    public RainbowMapView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public RainbowMapView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }


    // --------------------------------------- Public Properties
    /**
     * Change listener, used to trap map changes
     *
     */
    public interface OnChangeListener {
        public void onChange(RainbowMapView view, GeoPoint newCenter, GeoPoint oldCenter, int newZoom, int oldZoom);
    }
    
    protected boolean mNotifyChangesOnlyWhenCompled;

    public void setNotifyChangesOnlyWhenCompled(boolean newValue) {
        mNotifyChangesOnlyWhenCompled = newValue;
    }
    
    
    // -------------------------------------------------- Events
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        // Set touch internal
        mIsTouched = (ev.getAction() != MotionEvent.ACTION_UP);
        
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            mIsTouchEnded = false;
        } else if (ev.getAction() == MotionEvent.ACTION_UP) {
            mIsTouchEnded = true;
        }
        
        try {
            return super.onTouchEvent(ev);
        } catch (Exception e) {
            //shit may happen :(
            return false;
        }
    }
    
    @Override
    public void computeScroll() {
        super.computeScroll();
        
        //isZoomChange can happen also when the user press on Zoom control views,
        // so outside a panning / zooming gesture. For this reason, is always put
        // with an OR, independent, clause 
        
        //checks only when the user release the map view
        if (mNotifyChangesOnlyWhenCompled) {
            AppEnv.i(getContext()).getLogFacility().v("MainMapActivity", "mIsTouchEnded: " + mIsTouchEnded + ", isSpan: " + getMapCenter() + " - " + mLastMapCenter);
            if ((mIsTouchEnded 
                    && isSpanChange(getMapCenter(), mLastMapCenter, mEnableSpanTolerance, getZoomLevel())) 
                    || isZoomChange(getZoomLevel(), mLastZoomLevel))
            {
                AppEnv.i(getContext()).getLogFacility().v("MainMapActivity", "alert listener");
                //takes value aligned
                mLastMapCenter = getMapCenter();
                mLastZoomLevel = getZoomLevel();
                if (null != mChangeListener) {
                    mChangeListener.onChange(this, getMapCenter(), mLastMapCenter, getZoomLevel(), mLastZoomLevel);
                }
                
                //reset the condition
                mIsTouchEnded = false;
            }

        //checks every time the use move the map
        } else {
            if ((mIsTouched 
                    && isSpanChange(getMapCenter(), mLastMapCenter, mEnableSpanTolerance, getZoomLevel()))
                    || isZoomChange(getZoomLevel(), mLastZoomLevel))
            {
                //takes value aligned
                mLastMapCenter = getMapCenter();
                mLastZoomLevel = getZoomLevel();
                //if something has changed
                if (null != mChangeListener) {
                    mChangeListener.onChange(this, getMapCenter(), mLastMapCenter, getZoomLevel(), mLastZoomLevel);
                }
            }
        }

    }
    
    
    // ------------------------------------------ Public Methods
    
    /**
     * 
     * @param newValue
     */
    public void setOnChangeListener(OnChangeListener newValue) {
        setOnChangeListener(newValue, false);
    }
    
    /**
     * 
     * @param newValue
     * @param spanTolerance
     */
    public void setOnChangeListener(OnChangeListener newValue, boolean enableSpanTolerance) {
        mChangeListener = newValue;
        mEnableSpanTolerance = enableSpanTolerance;
    }
    
    public int[][] getBounds() {
        GeoPoint center = getMapCenter();
        int latitudeSpan = getLatitudeSpan();
        int longtitudeSpan = getLongitudeSpan();
        int[][] bounds = new int[2][2];

        bounds[0][0] = center.getLatitudeE6() - (latitudeSpan / 2);
        bounds[0][1] = center.getLongitudeE6() - (longtitudeSpan / 2);

        bounds[1][0] = center.getLatitudeE6() + (latitudeSpan / 2);
        bounds[1][1] = center.getLongitudeE6() + (longtitudeSpan / 2);
        return bounds;
    }
    
    // ----------------------------------------- Private Methods
    
    /**
     * 
     * @param newCenter
     * @param oldCenter
     * @param spanTolerance
     * @return
     */
    private boolean isSpanChange(GeoPoint newCenter, GeoPoint oldCenter, boolean enableSpanTolerance, int newZoomLevel) {
        if (enableSpanTolerance) {
            int spanTolerance = findToleranzaForZoomLevel(newZoomLevel);
            AppEnv.i(getContext()).getLogFacility().v("MainMapActivity", "spanTolerance: " + spanTolerance);
            return Math.abs(newCenter.getLatitudeE6() - oldCenter.getLatitudeE6()) >= spanTolerance
                    || Math.abs(newCenter.getLongitudeE6() - oldCenter.getLongitudeE6()) >= spanTolerance;
        } else {
            return !newCenter.equals(oldCenter);
        }
    }

    /**
     * Find an appropriate tolerance based on zoom level.
     * @param newZoomLevel
     * @return
     */
    private int findToleranzaForZoomLevel(int newZoomLevel) {
        //empiric relevation
        // zoom 21: 100 microdegrees of tolerance (y1, x1)
        // zoom 19: 300 microdegrees of tolerance
        // zoom 10: 70000 microdegrees of tolerance (y2, x2)
        // so, I use the equation of a line between two points
        //
        // (y - y1)    (x - x1)
        // --------- = ---------
        // (y2 - y1)   (x2 - x1)
        return (int) ((newZoomLevel - 21) / (10.0 - 21.0) * (70000 - 100) + 100);
    }

    /**
     * 
     * @param newZoomLevel
     * @param oldZoomLevel
     * @return
     */
    private boolean isZoomChange(int newZoomLevel, int oldZoomLevel) {
        return (newZoomLevel != oldZoomLevel);
    }
    
    
    // ----------------------------------------- Private Classes
}
